package com.gitee.wsl.security.hash.bcrypt

import com.gitee.wsl.io.Charset
import com.gitee.wsl.io.buffer.ByteBuffer
import com.gitee.wsl.lang.bytearray.Bytes
import com.gitee.wsl.lang.bytearray.secureWipe
import com.gitee.wsl.text.format.format

/**
 * Formats the out hash message of bcrypt. Usually this is the Modular Crypt Format.
 * Example
 * <pre>
 * $2a$12$US00g/uMhoSBm.HiuieBjeMtoN69SN.GE25fCpldebzkryUyopws6
</pre> *
 *
 *
 * Which consists of the version identifier:
 *
 * <pre>
 * $2a$
</pre> *
 *
 *
 * the cost factor:
 *
 * <pre>
 * 12$
</pre> *
 *
 *
 * 16 bytes Radix64 encoded UTF-8 bytes of salt:
 *
 * <pre>
 * uMhoSBm.HiuieBjeMtoN69
</pre> *
 *
 *
 * and 23 Radix64 encoded UTF-8 bytes of actual bcrypt hash
 *
 * <pre>
 * MtoN69SN.GE25fCpldebzkryUyopws6
</pre> *
 *
 *
 * The literal `$` is a simple separator
 *
 *
 * see: [modular_crypt_format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html)
 */
interface BCryptFormatter {
    /**
     * Create a message for the given raw hash data
     *
     * @param hashData to create a message from
     * @return message as bytes which might be UTF-8 encoded
     */
    fun createHashMessage(hashData: BCrypt.HashData): ByteArray

    /**
     * Default implantation following the Modular Crypt Format
     */
    class Default(private val encoder: Radix64Encoder,
                  private val defaultCharset: Charset = Charset.UTF8
    ) : BCryptFormatter {

        override fun createHashMessage(hashData: BCrypt.HashData): ByteArray {
            val saltEncoded: ByteArray = encoder.encode(hashData.rawSalt)
            val hashEncoded: ByteArray = encoder.encode(hashData.rawHash)
            val costFactorBytes: ByteArray =defaultCharset.encode(String.format("%02d", hashData.cost))

            try {
                val byteBuffer: ByteBuffer = ByteBuffer.allocate(
                    hashData.version.versionIdentifier!!.size +
                            costFactorBytes.size + 3 + saltEncoded.size + hashEncoded.size
                )
                byteBuffer.put(BCrypt.SEPARATOR)
                byteBuffer.put(hashData.version.versionIdentifier)
                byteBuffer.put(BCrypt.SEPARATOR)
                byteBuffer.put(costFactorBytes)
                byteBuffer.put(BCrypt.SEPARATOR)
                byteBuffer.put(saltEncoded)
                byteBuffer.put(hashEncoded)
                return byteBuffer.contentBytes
            } finally {
                saltEncoded.shuffle()
                hashEncoded.shuffle()
                costFactorBytes.shuffle()
//                Bytes.wrapNullSafe(saltEncoded).secureWipe()
//                Bytes.wrapNullSafe(hashEncoded).secureWipe()
//                Bytes.wrapNullSafe(costFactorBytes).secureWipe()
            }
        }
    }
}
